package cn.webank.framework.concurrent;

import java.util.Random;
import java.util.concurrent.TimeUnit;

import org.springframework.util.Assert;

import cn.webank.framework.data.jedis.pool.JedisPartition;
import cn.webank.framework.data.jedis.pool.MultiJedisShardedPool;
import cn.webank.framework.exception.SysException;
import redis.clients.jedis.Jedis;

public class DistributedRedisLock implements DistributedLock {

	private MultiJedisShardedPool multiJedisShardedPool;
	private long expireTimeout;
	private TimeUnit expireTimeUnit;
	private String moduleId;

	private final static Random r = new Random();

	public DistributedRedisLock(String moduleId, MultiJedisShardedPool multiJedisShardedPool, long expireTimeout,
			TimeUnit expireTimeUnit) {
		this.multiJedisShardedPool = multiJedisShardedPool;
		this.expireTimeout = expireTimeout;
		this.expireTimeUnit = expireTimeUnit;
		this.moduleId = moduleId;
	}

	@Override
	public boolean tryLock(long tryLockTimeout, TimeUnit tryLockTimeUnit) {

		Assert.isTrue(tryLockTimeout > 0, "tryLockTimeout must lager than zero");
		Assert.notNull(moduleId, "moduleId is null");

		Jedis jedis = null;
		JedisPartition jedisShardedPool = null;

		long start = System.currentTimeMillis();
		long millis = tryLockTimeUnit.toMillis(tryLockTimeout);
		try {
			jedisShardedPool = multiJedisShardedPool.getJedisPartition(moduleId);
			jedis = jedisShardedPool.getReadWriteResource();// 必须是master节点上的操作
			do {
				String result = jedis.set("LOCK:" + moduleId, "x", "NX", "EX", expireTimeUnit.toSeconds(expireTimeout));
				if ("OK".equalsIgnoreCase(result)) {
					return true;
				}

				try {
					int nextInt = Math.abs(r.nextInt(100));
					Thread.sleep(nextInt);
				} catch (InterruptedException e) {
					throw new SysException("InterruptedException error", e);
				}

			} while ((System.currentTimeMillis() - start) < millis);
		} finally {
			if (jedisShardedPool != null) {
				jedisShardedPool.returnReadWriteResource(jedis);
			}
		}
		return false;

	}

	@Override
	public void unlock() {
		Jedis jedis = null;
		JedisPartition jedisShardedPool = null;
		try {
			jedisShardedPool = multiJedisShardedPool.getJedisPartition(moduleId);
			jedis = jedisShardedPool.getReadWriteResource();
			jedis.del(moduleId);
		} finally {
			if (jedis != null && jedisShardedPool != null) {
				jedisShardedPool.returnReadWriteResource(jedis);
			}
		}

	}

}
